import { debounce, distinct } from '../../utils.js'
import { getStorage, setStorage } from '../../bridge.js'
import { xmlToJSON } from '../../libs/xml2json.js'

const transformNav = (ncxData) => {
  const flatten = (arr) => {
    const res = []
    for (const item of arr) {
      const url = item.content[0]._attr.src._value
      res.push({
        name: item.navLabel[0].text[0]._text,
        url: url.split(/[#\?]/)[0],
      })
      if (item.navPoint) {
        res.push(...flatten(item.navPoint))
      }
    }
    return res
  }

  return flatten(ncxData.ncx[0].navMap[0].navPoint)
}

const blobToBase64 = (blob) => {
  const reader = new FileReader();
  reader.readAsDataURL(blob); 
  return new Promise((resolve) => {
    reader.onloadend = () => {
      resolve(reader.result)
    }
  })
}

const fetchAndTransformBook = async (url) => {
  const res = await fetch(url)
  const blob = res.blob()
  const zip = await window.JSZip.loadAsync(blob)
  console.warn(zip)
  const data = {
    bookName: '',
    uuid: '',
    navBase: null,
    nav: [],
    pages: [],
    images: {},
  }
  for (const filename in zip.files) {
    const file = zip.files[filename]
    if (filename.endsWith('ncx')) {
      const content = await file.async('string')
      data.navBase = filename.split('/').slice(0, -1).join('/')
      const ncxData = xmlToJSON.parseString(content)
      data.nav = distinct(transformNav(ncxData), 'url')
      data.bookName = ncxData.ncx[0].docTitle[0].text[0]._text
      const dtbUid = ncxData.ncx[0].head[0].meta.find(p => p._attr.name._value === 'dtb:uid')
      if (dtbUid) {
        data.uuid = dtbUid._attr.content._value
      }
    } else if (filename.endsWith('html')) {
      const content = await file.async('string')
      data.pages.push({
        filename,
        content,
      })
    } else if (/\.(png|jpg|jpeg)$/.test(filename)) {
      const blob = await file.async('blob')
      const url = await blobToBase64(blob)
      data.images[filename] = url
    }
  }

  data.pages.forEach((page) => {
    page.content = page.content.replace(/<img[^>]+src="\.\.\/([^"]+)"[^>]+\/>/g, (_, val) => {
      const base64 = data.images[val]
      if (base64) {
        return `<img src="${base64}" style="width: 100%;"/>`
      }
      return ''
    })
  })
  return data
}

const domParser = new DOMParser()

export default class Book {
  constructor({ url, uuid, bookName }) {
    this.url = url
    this.active = false
    this.uuid = uuid
    this.bookName = bookName
    this.nav = []
    this.navBase = ''
    this.pages = []
    this.pageIndex = 0
    this.scrollTop = 0
  }

  /* private */
  _renderBookContent() {
    const { container, nav, pages, navBase, pageIndex, scrollTop } = this
    const navItem = nav[pageIndex]
    if (!navItem) {
      container.innerHTML = '无内容'
      return
    }
    const path = `${navBase}/${navItem.url}`.replace(/^\//, '')
    const page = pages.find((p) => p.filename === path)
    if (!page) {
      container.innerHTML = '未找到页面'
      return
    }
    const dom = domParser.parseFromString(page.content, 'text/html')
    const body = dom.querySelector('body')
    container.innerHTML = body.innerHTML
    container.scrollTop = scrollTop
  }

  async _fetchBookInfo() {
    const bookInfo = await fetchAndTransformBook(this.url)
    this.uuid = bookInfo.uuid
    this.bookName = bookInfo.bookName
    this.nav = bookInfo.nav
    this.navBase = bookInfo.navBase
    this.pages = bookInfo.pages
  }

  async _loadBookPosition() {
    const bookCache = await getStorage(`book_${this.uuid}`)
    if (bookCache) {
      this.pageIndex = bookCache.pageIndex
      this.scrollTop = bookCache.scrollTop
    }
  }

  _updateBookPosition({ pageIndex, scrollTop }) {
    if (pageIndex || pageIndex === 0) {
      this.pageIndex = pageIndex
    }
    if (scrollTop || scrollTop === 0) {
      this.scrollTop = scrollTop
    }
    return setStorage(`book_${this.uuid}`, {
      pageIndex: this.pageIndex,
      scrollTop: this.scrollTop,
    })
  }

  _handleContainerScroll = debounce(() => {
    this._updateBookPosition({ scrollTop: this.container.scrollTop })
  })

  /* public */
  async render({ container }) {
    this.container = container
    this.active = true

    await this._fetchBookInfo()
    await this._loadBookPosition()
    this._renderBookContent()

    this.container.addEventListener('scroll', this._handleContainerScroll)
  }

  destroy() {
    if (this.container) {
      this.container.removeEventListener('scroll', this._handleContainerScroll)
    }
    this.active = false
  }

  isActive() {
    return this.active
  }

  renderNav() {
    const html = [
      `<div
        class="nav-item pointer hover"
        style="padding: 0 10px; line-height: 30px;"
        data-url="exit"
      >[退出本书]</div>`,
    ]
    html.push(
      ...this.nav.map(
        (p) => `<div
          class="nav-item pointer hover"
          style="padding: 0 10px; line-height: 30px;"
          data-url="${p.url}"
        >${p.name}</div>`
      )
    )
    return html.join('')
  }


  nextPage() {
    if (this.pageIndex < this.pages.length - 1) {
      this._updateBookPosition({ pageIndex: this.pageIndex + 1, scrollTop: 0 })
    }
    this._renderBookContent()
  }

  prevPage() {
    if (this.pageIndex > 0) {
      this._updateBookPosition({ pageIndex: this.pageIndex - 1, scrollTop: 0 })
    }
    this._renderBookContent()
  }

  goPageByUrl(url) {
    const index = this.nav.findIndex((p) => p.url === url)
    if (index !== -1) {
      this._updateBookPosition({ pageIndex: index, scrollTop: 0 })
    }
    this._renderBookContent()
  }
}
